//
// Created by vivi on 24/09/2017.
//

#include "HairInterpolationSimulator.h"
#include "HairReader.h"
#include <iostream>
#include <HairCore.h>

namespace XRwy {
	using namespace std;
	using namespace Eigen;

	void HairInterpolationSimulatorWeightsInfo::init(istream &input_stream) {
		READ_INT32(input_stream, nstrand_int32);
		READ_INT32(input_stream, nguide_int32);

		this->nstrand = nstrand_int32;
		this->nguide = nguide_int32;

		//read the guide_ids
		guide_ids = vector<size_t>(this->nguide, 0);
		for (size_t i = 0; i < this->nguide; ++i) {
			READ_INT32(input_stream, id_int32);
			guide_ids.push_back(static_cast<size_t>(id_int32));
		}

		//read the weights
		weights = vector<IDAndWeightList>(this->nstrand);
		for (size_t n = 0; n < this->nstrand; ++n) {
			auto & weight = weights[n];

			READ_INT32(input_stream, guide_strand_count_int32);
			//if it is a guide hair
			if (guide_strand_count_int32 == 0)
				weight.push_back(HairInterpolationSimulatorWeightsInfoIDAndWeight{n, 1.0f});
			else
				for (size_t i = 0; i < static_cast<size_t>(guide_strand_count_int32); ++i) {
					READ_INT32(input_stream, the_strand_guide_id_int32);
					READ_SINGLE(input_stream, the_strand_guide_weight);
					weight.push_back(HairInterpolationSimulatorWeightsInfoIDAndWeight{static_cast<size_t>(the_strand_guide_id_int32), the_strand_guide_weight});
				}
		}

#ifndef NDEBUG
		cout << "Information for WeightsInfo" << endl;
		cout << "Guide Hair ID: " << endl;
		for (const auto guide_id : guide_ids)
			cout << guide_id << " ";
		cout << "Strand Weight: " << endl;
		for (size_t i = 0; i < weights.size(); ++i) {
			const auto & weight = weights[i];
			cout << i << ": ";
			for (const auto & id_and_weight : weight)
				cout << id_and_weight.id << "(" << id_and_weight.weight << ") ";
			cout << endl;
		}
#endif
	}

	HairInterpolationSimulator::HairInterpolationSimulator(const HairReader &reader, const HairInterpolationSimulatorConfiguration &conf):
		HairBaseSimulator(new HairInterpolationSimulatorConfiguration(conf)),
		weights_info(conf.weights_file_path),
		base_simulator(reader.resample(weights_info.guide_ids), conf) {

		vector<ParticleView> views;

		particle_count = static_cast<size_t>(reader.get_particle_count());
		strand_count = static_cast<size_t>(reader.get_strand_count());

		assert(strand_count == weights_info.nstrand);

		pos_buffer = vector<Vector3f>(particle_count);
		vel_buffer = vector<Vector3f>(particle_count);
		copy_buffer = new float[particle_count * 3];

		strand_rest_root_pos = vector<Vector3f>();
		strand_rest_root_pos.reserve(strand_count);


		size_t cur = 0;
		for (const auto & strand : reader.get_strands()) {
			views.clear();

			for (const auto & position : strand) {
				pos_buffer[cur] = position;
				vel_buffer[cur] = Vector3f::Zero();

				views.push_back(ParticleView(&pos_buffer[cur], &vel_buffer[cur], true));
				++cur;
			}

			push_strand(views);
			strand_rest_root_pos.push_back(*views[0].pos_ptr);
		}
	}

	void HairInterpolationSimulator::interpolation(const Affine3f & transformation, const float t) {
		base_simulator.copy_particles(copy_buffer);

		//copy the guide hair strand
		float *cur = copy_buffer;
		for (size_t guide_id : weights_info.guide_ids)
			for (size_t particle_id : strands[guide_id].all_ids) {

				auto & particle = particles[particle_id];

				Vector3f new_pos;
				new_pos << cur[0], cur[1], cur[2];

				particle.vel() = (new_pos - particle.pos()) / t;
				particle.pos() = new_pos;

				cur += 3;
			}

		for (size_t i = 0; i < strand_count; ++i) {

			const auto & weights = weights_info.weights[i];
			auto & strand = strands[i];

			for (size_t particle_id : strand.all_ids)
				particles[particle_id].pos() = Vector3f::Zero();

			for (const auto & w : weights) {
				const auto & itp_strand = strands[w.id];
				for (size_t i = 0; i < strand.all_ids.size(); ++i)
					particles[strand.all_ids[i]].pos() += w.weight * particles[itp_strand.all_ids[i]].pos();
			}

			particles[strand.all_ids[0]].pos() = transformation * strand_rest_root_pos[i];
		}
	}

	void HairInterpolationSimulator::strand_dynamic(const Affine3f & transformation, const float t) {
		base_simulator.strand_dynamic(transformation, t);

//		//FIXME: delete copy buffer
//		float *cur = copy_buffer;
//		for (const auto & particle : base_simulator.particles) {
//			const auto & pos = *(particle.pos_ptr);
//			cur[0] = pos(0);
//			cur[1] = pos(1);
//			cur[2] = pos(2);
//
//			cur += 3;
//		}
		base_simulator.copy_particles(copy_buffer);

		interpolation(transformation, t);
	}
}